using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Input.Touch;
using Microsoft.Xna.Framework.Media;

namespace MarbleAddingMachine
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        Vector2 pos = Vector2.Zero;

#if WINDOWS_PHONE
        TouchCollection pState;
#else
        MouseState pState;
#endif

#if WINDOWS
        RenderTarget2D rt;
        Texture2D screen;
#endif
        bool[] bits = new bool[6];
        bool[] inputRack = new bool[6];
        bool[] outputRack = new bool[6];
        bool[] marbleArrived = new bool[6];
        bool[] chngState = new bool[6];
        bool sendMarbles = false;
        bool outputting = false;
        float[] seeSawRotations = new float[] { -25, -25, -25, -25, -25, -25 };
       // Vector2[] seeSawPosistions = new Vector2[] { new Vector2(103, 80), new Vector2(103, 80), new Vector2(103, 80), new Vector2(103, 80), new Vector2(103, 80), new Vector2(120, 141) };
        
        Texture2D wireFrameUpper;
        Texture2D wireFrameLower;
        Texture2D seeSaw;
        Texture2D marble;
        Texture2D inputSlide;
        Texture2D overLay;
        Texture2D outputSlide;

        Rectangle fullScreen;

        SpriteFont font;

        List<Marble> marbles = new List<Marble>();

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // Frame rate is 30 fps by default for Windows Phone
            TargetElapsedTime = TimeSpan.FromTicks(333333);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            // TODO: Add your initialization logic here
#if WINDOWS
            graphics.PreferredBackBufferWidth = 480;
            graphics.PreferredBackBufferHeight = 800;
            graphics.ApplyChanges();
            IsMouseVisible = true;
#endif
            base.Initialize();
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);
            wireFrameUpper = Content.Load<Texture2D>("wireFrameUpper");
            wireFrameLower = Content.Load<Texture2D>("wireFrameLower");
            seeSaw = Content.Load<Texture2D>("seeSaw");
            marble = Content.Load<Texture2D>("Marble");
            inputSlide = Content.Load<Texture2D>("inputSlide");
            outputSlide = Content.Load<Texture2D>("outputSlide");
            overLay = new Texture2D(GraphicsDevice, 1, 1);
            overLay.SetData(new Color[] { Color.White });
            font = Content.Load<SpriteFont>("BaseFont");
            //testMarble = new Marble(MarbleAddingMachine.Marble.MarbleState.DroppingFirst, new Vector2(39.5f, (int)50f + ((4) * 72.5f)), marble, 1);
#if WINDOWS
            rt = new RenderTarget2D(GraphicsDevice, 800, 480);
            fullScreen = new Rectangle(0, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferWidth, graphics.PreferredBackBufferHeight);
#else
            fullScreen = new Rectangle(0, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferHeight, graphics.PreferredBackBufferWidth);
#endif

            // TODO: use this.Content to load your game content here
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            foreach (Marble m in marbles)
            {
                m.Update(seeSawRotations, ref chngState, ref bits, outputting);
                if (m.state == Marble.MarbleState.RollingExit)
                {
                    foreach (Marble m2 in marbles)
                    {
                        if (m2.state == Marble.MarbleState.RollingExit)
                        {
                            if (m.GetHashCode() != m2.GetHashCode())
                            {
                                float dist = Vector2.Distance(m.position, m2.position);
                                if (dist < 26)
                                {
                                    m2.Spd = m.Spd;
                                    if (m.position.X < m2.position.X)
                                    {
                                        m2.position += (26 - dist) * (float)Math.Sqrt(2) * m.exitRampAccel;
                                    }
                                    else
                                    {
                                        m.position += (26 - dist) * (float)Math.Sqrt(2) * m.exitRampAccel;
                                    }
                                }
                            }
                        }
                    }
                    
                }
            } 
            for (int i = 0; i < marbles.Count; i++)
            {
                if (marbles[i].state == Marble.MarbleState.Deletable)
                {
                    marbles.RemoveAt(i);
                    i--;
                }
            }

#if WINDOWS_PHONE
            TouchCollection cState = TouchPanel.GetState();

            if (cState.Count > 0 && pState.Count == 0)
            {
#else
            MouseState cState = Mouse.GetState();

            if(cState.LeftButton == ButtonState.Pressed && pState.LeftButton == ButtonState.Released)
            {
#endif
               
                bool fnd = false;
                if (!outputting)
                {
                    for (int i = 0; i < 6; i++)
                    {
#if WINDOWS_PHONE
                    if( new Rectangle(15, 30 + (int)((5.0f - i) * 72.5f), 55, 55).Contains((int)cState[0].Position.X, (int)cState[0].Position.Y))
                    {
#else
                        if (new Rectangle((30 + (int)((5.0f - i) * 72.5f)), 15, 55, 55).Contains(480 - cState.X, cState.Y))
                        {
#endif
                            fnd = true;
                            inputRack[i] = !inputRack[i];
                        }
                    }
                }
#if WINDOWS_PHONE
                pos = cState[0].Position;
#else
                pos = new Vector2(cState.X, cState.Y);
#endif

#if WINDOWS_PHONE
                if(new Rectangle(713, 0, 87, 88).Contains((int)cState[0].Position.X, (int)cState[0].Position.Y))
                {
#else
                if (new Rectangle(0, 713, 87, 88).Contains(480 - cState.X, cState.Y))
                {
#endif
                    fnd = true;
                    outputting = !outputting;

                    if (outputting)
                    {
                        outputRack = (bool[])bits.Clone();
                        chngState = (bool[])bits.Clone();
                        bits = new bool[6];
                        marbleArrived = new bool[6];

                    }
                    else 
                    {
                        outputRack = new bool[6];
                    }

                }
                
                if (!fnd && !outputting)
                {
                    
                    sendMarbles = true;
                }
                //inputRack = new bool[] { false, false, false, false, false, true };
            }

            if (sendMarbles)
            {
                //bits = AddBits(bits, inputRack);
                for (int i = 0; i < 6; i++)
                {
                    if (inputRack[i])
                    {
                        marbles.Add(new Marble(MarbleAddingMachine.Marble.MarbleState.DroppingFirst, new Vector2(39.5f,  80 + ((5 - i) * 64)), marble, i));
                    }
                }
                    inputRack = new bool[6];
                /*seeSawTransition = true;
                for (int i = 0; i < 6; i++)
                {
                    bool fnd = false;
                    if (bits[i] && seeSawRotations[i] < 25)
                    {
                        fnd = true;
                    }
                    else if (!bits[i] && seeSawRotations[i] > -25)
                    {
                        fnd = true;
                    }
                    marbleArrived[i] = !fnd;
                }*/
                sendMarbles = false;
            }
            for (int i = 0; i < 6; i++)
            {
                if (chngState[i])
                {
                    bool fnd = false;
                    if (bits[i] && seeSawRotations[i] < 25)
                    {
                        fnd = true;
                        seeSawRotations[i] += 12.5f;
                    }
                    else if (!bits[i] && seeSawRotations[i] > -25)
                    {
                        fnd = true;
                        seeSawRotations[i] -= 12.5f;
                    }
                    if (!fnd)
                    {
                        chngState[i] = false;
                    }
                }
            }
#if WINDOWS_PHONE
            pState = TouchPanel.GetState();
#else
            pState = Mouse.GetState();
#endif
            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

#if WINDOWS
            GraphicsDevice.SetRenderTarget(rt);
            GraphicsDevice.Clear(Color.CornflowerBlue);
#endif

            spriteBatch.Begin();
            foreach (Marble m in marbles)
            {
                m.Draw(spriteBatch);
            }
            spriteBatch.Draw(inputSlide, fullScreen, null, Color.White, MathHelper.ToRadians(-90), Vector2.Zero, SpriteEffects.None, 0);
            
            spriteBatch.Draw(wireFrameUpper, fullScreen, null, Color.White, MathHelper.ToRadians(-90), Vector2.Zero, SpriteEffects.None, 0);
            spriteBatch.Draw(wireFrameLower, fullScreen, null, Color.White, MathHelper.ToRadians(-90), Vector2.Zero, SpriteEffects.None, 0);

            for (int i = 0; i < 6; i++)
            {
                spriteBatch.Draw(seeSaw, new Vector2(120 + ((5 - i) * 60), 80 + ((5 - i) * 64)), null, Color.White, MathHelper.ToRadians(-90 + seeSawRotations[i]), new Vector2(46, 17), 1f, SpriteEffects.None, 0);
                if (bits[i] && marbleArrived[i])
                {
                    spriteBatch.Draw(marble, new Vector2(119 + ((5 - i) * 60), 25 + ((5 - i) * 64)), null, Color.White, 0, new Vector2(marble.Width / 2, marble.Height / 2), 1f, SpriteEffects.None, 0);
                }

                if (inputRack[i])
                {
                    spriteBatch.Draw(marble, new Vector2(42.5f,(int)61f + ((5.0f - i) * 72.5f)), null, Color.White, 0, new Vector2(marble.Width / 2, marble.Height / 2), 1f, SpriteEffects.None, 0);
                }
                //if (outputRack[i] && marbleArrived[i])
                //{
                //    spriteBatch.Draw(marble, new Vector2(555f, (int)48f + ((5.0f - i) * 64f)), null, Color.White, 0, new Vector2(marble.Width / 2, marble.Height / 2), 1f, SpriteEffects.None, 0); 
                //}
            }
            spriteBatch.Draw(outputSlide, fullScreen, null, Color.White, MathHelper.ToRadians(-90), Vector2.Zero, SpriteEffects.None, 0);

            if (outputting)
            {
                Vector2 size = font.MeasureString(ToDec(outputRack).ToString());
                spriteBatch.DrawString(font, ToDec(outputRack).ToString(), new Vector2(758.5f, 241), Color.Black, MathHelper.ToRadians(-90), size / 2f, 1f, SpriteEffects.None, 0);
            }
            else
            {
                spriteBatch.DrawString(font, string.Join(",", GetBitsStr(bits)), new Vector2(650, 300), Color.Yellow, MathHelper.ToRadians(-90), Vector2.Zero, 1f, SpriteEffects.None, 0);
            }
            spriteBatch.End();
#if WINDOWS
            GraphicsDevice.SetRenderTarget(null);
            screen = rt;
            spriteBatch.Begin();
            spriteBatch.Draw(screen,new Vector2(screen.Height,0),null,Color.White,MathHelper.ToRadians(90),Vector2.Zero,1f,SpriteEffects.None,0);
            spriteBatch.End();
#endif
            // TODO: Add your drawing code here

            base.Draw(gameTime);
        }
        
        private byte ToDec(bool[] bits)
        {
            byte outNum = 0;

            for (int i = 0; i < 6; i++)
            { 
                if(bits[i])
                {
                    outNum += (byte)Math.Pow(2, 5 - i);
                }
            }

            return outNum;
        }

        private string[] GetBitsStr(bool[] bits)
        {
            bool[] outBits = bits;
            string[] outStr = new string[6];
            
            for (int i = 0; i < 6; i++)
            {
                if (outBits[i]) outStr[i] = "1";
                else outStr[i] = "0";
            }

            return outStr;
        }

        private bool[] AddBits(bool[] bits1, bool[] bits2)
        {
            bool carry = false;
            bool[] outBits = new bool[6];

            for (int i = 5; i >= 0; i--)
            {
                outBits[i] = bits1[i] ^ bits2[i] ^ carry;
                carry = bits1[i] & bits2[i] | carry & (bits1[i] | bits2[i]);
            }

            return outBits;
        }

        private bool[] AndBits(bool[] bits1, bool[] bits2)
        {
            bool[] outBits = new bool[6];

            for (int i = 5; i >= 0; i--)
            {
                outBits[i] = bits1[i] & bits2[i];
            }

            return outBits;
        }

    }
}
